tg-me.com/golang_lib/492
Last Update:
Анти-функциональные опции в Go
Часто в Go можно встретить такую конструкцию:
type Options struct {
Timeout time.Duration
Retries int
Logger *log.Logger
}
func DoSomething(ctx context.Context, opts Options) error {
if opts.Timeout == 0 {
opts.Timeout = 5 * time.Second
}
if opts.Retries == 0 {
opts.Retries = 3
}
if opts.Logger == nil {
opts.Logger = log.Default()
}
// дальше используем opts
}
На первый взгляд — удобно. Но на практике это ведёт к скрытым багам и неочевидному поведению. Почему?
🔸 Проблема 1: нулевое значение может быть валидным
Допустим, я хочу отключить ретраи и передаю
Retries: 0
. Но функция решает, что "ноль — это дефолт", и перезаписывает его на 3. В итоге получается поведение, которого явно не хотел.🔸 Проблема 2: смешение ответственности
Функция
DoSomething
теперь делает больше, чем нужно: она и бизнес-логику выполняет, и значения инициализирует. Это противоречит принципу единственной ответственности и усложняет тестирование.🔸 Проблема 3: дублирование
Если в коде много таких функций, каждая будет по-своему инициализировать
Options
. Это ведёт к дублированию и рассыпанной логике дефолтов.💡 Что делать?
Вынеси дефолтные значения в отдельную функцию:
func DefaultOptions() Options {
return Options{
Timeout: 5 * time.Second,
Retries: 3,
Logger: log.Default(),
}
}
Теперь клиентский код выглядит явно:
opts := DefaultOptions()
opts.Retries = 0 // без ретраев
DoSomething(ctx, opts)
https://rednafi.com/go/dysfunctional_options_pattern/
👉 @golang_lib
BY Библиотека Go (Golang) разработчика

Share with your friend now:
tg-me.com/golang_lib/492